`timescale 1ns / 1ps

module arc_cpu (
	input  wire clk,
	input  wire aresetn,
	input  wire step,
	input  wire clk_mem,

	output wire [31:0] imem_req_addr,
	output wire [31:0] imem_req_data,
	output wire imem_req_wen,
	output wire imem_req_valid,
	input wire  imem_resp_valid,
	input wire  [31:0] imem_resp_data,

	output wire [31:0] dmem_req_addr,
	output wire [31:0] dmem_req_data,
	output wire dmem_req_wen,
	output wire dmem_req_valid,
	input wire  dmem_resp_valid,
	input wire  [31:0] dmem_resp_data,

	output wire [31:0] address,
	output wire [31:0] data_out,
	input  wire [31:0] data_in,
	input  wire [31:0] chip_debug_in,
	output wire [31:0] chip_debug_out0,
	output wire [31:0] chip_debug_out1,
	output wire [31:0] chip_debug_out2,
	output wire [31:0] chip_debug_out3
	);

	// cache control
	reg 		icache_en;
	reg        	icache_wen;
	reg [31:0] 	icache_A_addr;
	wire [31:0] icache_A_in;
	wire [31:0] icache_A_out;
	wire		icache_stall;
	/* Debug inout */
	wire [9:0] 	icache_B_addr;
	assign icache_B_addr = icache_A_addr[11:2]; 
//	wire [9:0] 	icache_B_addr = chip_debug_in[25:16];
	wire [31:0] icache_B_out;
	wire [7:0] 	icache_tag_seq;
	wire 		icache_dirty_bit;

	reg 		dcache_en;
	reg        	dcache_wen;
	reg [31:0] 	dcache_A_addr;
	reg [31:0] 	dcache_A_in;
	wire [31:0] dcache_A_out;
	wire		dcache_stall;
	/* Debug inout */
	wire [9:0] 	dcache_B_addr;
	assign dcache_B_addr = dcache_A_addr[11:2];
//	wire [9:0] 	dcache_B_addr = chip_debug_in[25:16];
	wire [31:0] dcache_B_out;
	wire [7:0] 	dcache_tag_seq;
	wire 		dcache_dirty_bit;



	// Cache debug Output
	wire [31:0] icache_content, icache_tag;
	wire [31:0] dcache_content, dcache_tag;

	// stage registers
	wire [31:0] if_in_ir;
	reg [31:0] if_in_pc;

	reg  [31:0] if_reg_pc, if_reg_ir;


	wire [31:0] if_out_pc, if_out_ir;

	reg  [31:0] id_reg_pc, id_reg_ir;


	wire [31:0] id_out_pc, id_out_ir, id_out_reg_value, id_out_alu_src_a, id_out_alu_src_b;
	wire [4:0] id_out_wd, id_out_shamt;
	wire id_out_reg_write, id_out_cond;
	wire [5:0] id_out_alu_op;

	reg [31:0] ex_reg_pc, ex_reg_ir, ex_reg_reg_value, ex_reg_alu_src_a, ex_reg_alu_src_b;
	reg [4:0] ex_reg_wd, ex_reg_shamt;
	reg ex_reg_reg_write, ex_reg_cond;
	reg [5:0] ex_reg_alu_op;


	wire [31:0] ex_out_pc, ex_out_ir, ex_out_reg_value, ex_out_alu_out, ex_out_jmpaddr;
	wire [4:0] ex_out_wd;
	wire ex_out_reg_write, ex_out_cond, ex_out_jmp;

	reg [31:0] me_reg_pc, me_reg_ir, me_reg_reg_value, me_reg_alu_out;
	reg [4:0] me_reg_wd;
	reg me_reg_reg_write;


	wire [31:0] me_out_pc, me_out_ir, me_out_alu_out;
	wire [4:0] me_out_wd;
	wire me_out_reg_write;

	reg [31:0] wb_reg_pc, wb_reg_ir,  wb_reg_alu_out;
	reg [4:0] wb_reg_wd;
	reg wb_reg_reg_write;
	wire [31:0] wb_data;

	// regs
	reg [31:0] regs[31:0];

	// stall ctrl
	wire reg_tbw_rs, reg_tbw_rt;

	wire st_hazard, stall;
	reg [1:0] st_branch;


	integer i;
	always @(posedge clk) begin 
		if (aresetn) begin
			if (step && (~st_hazard) && (~icache_en)) begin
				if (ex_out_jmp) if_in_pc <= ex_out_jmpaddr;
				else if (ex_out_cond) if_in_pc <= ex_out_alu_out;
				else if_in_pc <= if_reg_pc + 4;
			end 
			else if_in_pc <= if_in_pc;
		end 
		else if_in_pc <= 0;
	end 

	reg icache_stall_prev, dcache_stall_prev, st_hazard_prev;


	// IF
	always @(posedge clk) begin
		if (aresetn) begin
			if (step && (~st_hazard)) begin
				if_reg_pc <= if_in_pc;
				if_reg_ir <= if_in_ir;
			end 
			else begin
				if_reg_pc <= if_reg_pc;
				if_reg_ir <= if_reg_ir;
			end
			icache_stall_prev <= icache_stall;
			dcache_stall_prev <= dcache_stall;
			st_hazard_prev <= st_hazard;
			if (icache_en == 1'b1 && (~st_hazard)) icache_A_addr <= if_reg_pc;
			if (icache_en == 1'b0 && (~st_hazard)) icache_en <= 1'b1;
			else if (~st_hazard && st_hazard_prev) 
				icache_en <= 1'b0;
			
		end
		else begin
			if_reg_pc <= 0;
			if_reg_ir <= 0;
			icache_en <= 1'b0;
			icache_wen <= 1'b0;
			icache_A_addr <= 0;
		end
	end

	
icache_ctrl icache_inst (
    .clk(clk), 
    .rst(~aresetn), 
    .clk_mem(clk_mem), 
    .cache_req_addr(icache_A_addr), 
    .cache_req_data(0), 
    .cache_req_wen(1'b0), 
    .cache_req_valid(icache_en), 
    .cache_resp_data(icache_A_out), 
    .cache_resp_stall(icache_stall), 
	 
    .mem_req_addr(imem_req_addr), 
    .mem_req_data(imem_req_data), 
    .mem_req_wen(imem_req_wen), 
    .mem_req_valid(imem_req_valid), 
    .mem_resp_valid(imem_resp_valid), 
    .mem_resp_data(imem_resp_data), 
	 
    .cache_debug_addr(icache_B_addr), 
    .cache_debug_data(icache_B_out), 
    .cache_debug_dirty_bit(icache_dirty_bit), 
    .cache_debug_tag_seq(icache_tag_seq)
    );


	assign if_out_ir = icache_A_out;
	assign if_out_pc = icache_A_addr;

	// ID
	always @(posedge clk) begin
		if (aresetn) begin
			if(step && (~st_hazard)) begin
				if (ex_out_jmp) id_reg_pc <= if_in_pc;
				else if (ex_out_cond) id_reg_pc <= if_in_pc;
				else id_reg_pc <= if_out_pc;
				id_reg_ir <= if_out_ir;
			end else begin
				id_reg_pc <= id_reg_pc;
				id_reg_ir <= id_reg_ir;
			end
		end
		else begin
			id_reg_pc <= 0;
			id_reg_ir <= 0;
		end
	end

	wire [4:0] rs;
	wire [4:0] rt;
	wire [4:0] wd;
	wire [31:0] imm;
	wire req_rs, req_rt;

	decoder decoder_inst(
		.ir(id_reg_ir),
		.rs(rs),
		.require_rs(req_rs),
		.rt(rt),
		.require_rt(req_rt),
		.wd(id_out_wd),
		.reg_write(id_out_reg_write),
		.imm(imm),
		.alu_op(id_out_alu_op),
		.shamt(id_out_shamt)
	); 

	assign reg_tbw_rs = (rs != id_out_wd && rs == ex_out_wd || rs == me_out_wd || rs == wb_reg_wd) & rs != 0;
	assign reg_tbw_rt = (rt != id_out_wd && rt == ex_out_wd || rt == me_out_wd || rt == wb_reg_wd) & rt != 0;
	always @(posedge clk) begin 
		if (~aresetn) begin
			st_branch <= 0;
		end 

		if (ex_out_cond | ex_out_jmp) st_branch <= 2'b10;
		if (st_branch > 0) st_branch <= st_branch - 1'b1;
	end 

	assign st_hazard = (reg_tbw_rs && req_rs) || (reg_tbw_rt && req_rt) || icache_stall || dcache_stall;
	assign stall = st_hazard | (ex_out_cond | ex_out_jmp);





	assign id_out_pc = id_reg_pc;
	assign id_out_ir = id_reg_ir;
	assign id_out_reg_value = regs[rt];
	assign id_out_alu_src_a = (req_rs && ~(id_reg_ir[31:27] == 5'b00010)) ? regs[rs] : (id_reg_pc + 4);
	assign id_out_alu_src_b = (req_rt && ~(id_reg_ir[31:27] == 5'b00010) 
		&& ~(id_reg_ir[31:26] == 6'b101011) && ~(id_reg_ir[31:26] == 6'b100011)) ? regs[rt] : imm;

	assign id_out_cond = (regs[rs] == regs[rt]) ? 1'b1 : 1'b0;

	// EXE
	always @(posedge clk) begin
		if (aresetn && (~stall)) begin
			if (step) begin
				ex_reg_pc <= id_out_pc;
				ex_reg_ir <= id_out_ir;
				ex_reg_wd <= id_out_wd;
				ex_reg_reg_write <= id_out_reg_write;
				ex_reg_reg_value <= id_out_reg_value;
				ex_reg_alu_src_a <= id_out_alu_src_a;
				if (id_out_ir[31:27] == 5'b00010) ex_reg_alu_src_b <= {id_out_alu_src_b[29:0], 2'b00};
					else ex_reg_alu_src_b <= id_out_alu_src_b;
				ex_reg_alu_op <= id_out_alu_op;
				ex_reg_shamt <= id_out_shamt;
				ex_reg_cond <= id_out_cond;
			end else begin
				ex_reg_pc <= ex_reg_pc;
				ex_reg_ir <= ex_reg_ir;
				ex_reg_wd <= ex_reg_wd;
				ex_reg_reg_write <= ex_reg_reg_write;
				ex_reg_reg_value <= ex_reg_reg_value;
				ex_reg_alu_src_a <= ex_reg_alu_src_a;
				ex_reg_alu_src_b <= ex_reg_alu_src_b;
				ex_reg_alu_op <= ex_reg_alu_op;
				ex_reg_shamt <= ex_reg_shamt;
				ex_reg_cond <= ex_reg_cond;
			end
		end
		else begin
			ex_reg_pc <= 0;
			ex_reg_ir <= 0;
			ex_reg_wd <= 0;
			ex_reg_reg_write <= 0;
			ex_reg_reg_value <= 0;
			ex_reg_alu_src_a <= 0;
			ex_reg_alu_src_b <= 0;
			ex_reg_alu_op <= 0;
			ex_reg_shamt <= 0;
			ex_reg_cond <= 0;
		end
	end

	alu alu_inst(
		.A(ex_reg_alu_src_a), 
		.B(ex_reg_alu_src_b), 
		.Shamt(ex_reg_shamt),
		.ALU_Func(ex_reg_alu_op), 
		.res(ex_out_alu_out), 
		.zero(zero), 
		.overflow(overflow)
	);

	wire branch = ex_reg_cond ^ ex_reg_ir[26];

	assign ex_out_jmp = ((ex_reg_ir[31:27] == 5'b00001) || ((ex_reg_ir[31:27] == 5'b00000) && (ex_reg_ir[5:0] == 6'b001000))) ? 1 : 0;
	assign ex_out_jmpaddr = ex_reg_ir[31:27] == 5'b00000 ? 
				regs[ex_reg_ir[25:21]] : {ex_reg_pc[31:28], ex_reg_ir[25:0], 2'b0};

	assign ex_out_cond = (ex_reg_ir[31:27] == 5'b00010) ? branch : 0;
	assign ex_out_pc = ex_reg_pc & {32{~st_branch[0]}};
	assign ex_out_ir = ex_reg_ir & {32{~st_branch[0]}};
	assign ex_out_wd = (ex_out_ir[31:26] == 6'b101011) ? 0 : (ex_reg_wd & {5{~st_branch[0]}});
	assign ex_out_reg_write = ex_reg_reg_write & ~st_branch[0];
	assign ex_out_reg_value = ex_reg_reg_value & ~st_branch[0];

	// MEM
	always @(posedge clk) begin
		if (aresetn) begin
			if (step) begin
				me_reg_pc <= ex_out_pc; 
				me_reg_ir <= ex_out_ir; 
				me_reg_wd <= ex_out_wd; 
				me_reg_reg_write <= ex_out_reg_write; 
				me_reg_reg_value <= ex_out_reg_value; 
				me_reg_alu_out <= ex_out_alu_out; 
				if (mem_rw) begin 
					dcache_en <= 1'b1;
					dcache_wen <= mem_w;
					dcache_A_addr <= me_reg_alu_out;
					dcache_A_in <= me_reg_reg_value;
				end 
				if (dcache_stall_prev && ~dcache_stall) begin 
					dcache_en <= 1'b0;
					dcache_wen <= 1'b0;
					dcache_A_addr <= 0;
					dcache_A_in <= 0;
				end 
			end else begin
				me_reg_pc <= me_reg_pc; 
				me_reg_ir <= me_reg_ir; 
				me_reg_wd <= me_reg_wd; 
				me_reg_reg_write <= me_reg_reg_write; 
				me_reg_reg_value <= me_reg_reg_value; 
				me_reg_alu_out <= me_reg_alu_out; 
			end
		end
		else begin
			me_reg_pc <= 0;
			me_reg_ir <= 0;
			me_reg_wd <= 0;
			me_reg_reg_write <= 0;
			me_reg_reg_value <= 0;
			me_reg_alu_out <= 0;
			dcache_en <= 1'b0;
			dcache_wen <= 1'b0;
			dcache_A_addr <= 0;
			dcache_A_in <= 0;
		end
	end


	wire mem_rw = (me_reg_ir[31:26] == 6'b101011 || me_reg_ir[31:26] == 6'b100011) ? 1'b1 : 1'b0;
	wire mem_w = (me_reg_ir[31:26] == 6'b101011) ? 1'b1 : 1'b0;
	wire [31:0] dataout;

	assign dataout = dcache_A_out;

	
dcache_ctrl dcache_inst (
    .clk(clk), 
    .rst(~aresetn), 
    .clk_mem(clk_mem), 
    .cache_req_addr(dcache_A_addr), 
    .cache_req_data(dcache_A_in), 
    .cache_req_wen(dcache_wen), 
    .cache_req_valid(dcache_en), 
    .cache_resp_data(dcache_A_out), 
    .cache_resp_stall(dcache_stall), 
	 
    .mem_req_addr(dmem_req_addr), 
    .mem_req_data(dmem_req_data), 
    .mem_req_wen(dmem_req_wen), 
    .mem_req_valid(dmem_req_valid), 
    .mem_resp_valid(dmem_resp_valid), 
    .mem_resp_data(dmem_resp_data), 
	 
    .cache_debug_addr(dcache_B_addr), 
    .cache_debug_data(dcache_B_out), 
    .cache_debug_dirty_bit(dcache_dirty_bit), 
    .cache_debug_tag_seq(dcache_tag_seq)
    );

	assign me_out_pc = me_reg_pc;
	assign me_out_ir = me_reg_ir;
	assign me_out_wd = me_reg_wd;
	assign me_out_reg_write = me_reg_reg_write;
	assign me_out_alu_out = me_reg_alu_out;


	// WB
	always @(posedge clk) begin
		if (aresetn) begin
			if (step) begin
			wb_reg_pc <= me_out_pc;
			wb_reg_ir <= dcache_stall ? wb_reg_ir : me_out_ir;
			wb_reg_wd <= dcache_stall ? wb_reg_wd : me_out_wd;
			wb_reg_reg_write <= dcache_stall ? wb_reg_reg_write : me_out_reg_write;
			wb_reg_alu_out <= me_out_alu_out;
			end else begin
			wb_reg_pc <= wb_reg_pc;
			wb_reg_ir <= wb_reg_ir;
			wb_reg_wd <= wb_reg_wd;
			wb_reg_reg_write <= wb_reg_reg_write;
			wb_reg_alu_out <= wb_reg_alu_out;
			end
		end
		else begin
			wb_reg_pc <= 0;
			wb_reg_ir <= 0;
			wb_reg_wd <= 0;
			wb_reg_reg_write <= 0;
			wb_reg_alu_out <= 0;
		end
	end

	wire wb_refresh = dcache_stall_prev & ~dcache_stall;

	assign wb_data = (wb_refresh) ? dcache_A_out : 
		(wb_reg_ir[31:26] == 6'b100011) ? dataout : wb_reg_alu_out;

	// REG FILE

	always @(posedge clk) begin
		if (aresetn) begin
			if (step && wb_reg_reg_write && wb_reg_wd != 0) begin
				regs[wb_reg_wd] <= wb_data;
			end
			
		end
		else begin
			for (i = 0; i < 32; i = i + 1) begin
				regs[i] <= 32'b0;
			end
		end
	end

	// Icache Debug
	assign icache_content = icache_B_out[31:0];
	assign icache_tag = {6'b0, icache_B_addr, icache_tag_seq[7:0], 7'b0, icache_dirty_bit};
	// Dcache Debug
	assign dcache_content = dcache_B_out[31:0];
	assign dcache_tag = {6'b0, dcache_B_addr, dcache_tag_seq[7:0], 7'b0, dcache_dirty_bit};

	// Debug
	assign chip_debug_out0 =   regs[chip_debug_in[4:0]];
	assign chip_debug_out1 =   (chip_debug_in[7:5] == 0) ? if_out_pc : 
								(chip_debug_in[7:5] == 1) ? id_out_pc : 
								(chip_debug_in[7:5] == 2) ? ex_out_pc : 
								(chip_debug_in[7:5] == 3) ? me_out_pc : 
								wb_reg_pc;

	assign chip_debug_out2 =   (chip_debug_in[7:5] == 0) ? if_out_ir : 
								(chip_debug_in[7:5] == 1) ? id_out_ir : 
								(chip_debug_in[7:5] == 2) ? ex_out_ir : 
								(chip_debug_in[7:5] == 3) ? me_out_ir : 
								wb_reg_ir;
								
	assign chip_debug_out3 =   (chip_debug_in[11:10] == 0) ? icache_content :
								(chip_debug_in[11:10] == 1) ? icache_tag :
								(chip_debug_in[11:10] == 2) ? dcache_content :
								dcache_tag;
	

										
	wire [31:0] reg0 = regs[0];
	wire [31:0] reg1 = regs[1];
	wire [31:0] reg2 = regs[2];
	wire [31:0] reg3 = regs[3];
	wire [31:0] reg4 = regs[4];
	wire [31:0] reg5 = regs[5];
	wire [31:0] reg6 = regs[6];
	wire [31:0] reg7 = regs[7];


							
endmodule
